useContext 是能夠讓程式變得簡潔的利器,Component 不再需要經過一層一層的 Props 傳遞,便能直接使用在需要它的 Component。
少了 useContext 不會怎樣,但是多了它會很不一樣。
在學習 useContext 使用前,我們先打開 src/index.js,並將它更改為以下內容:

經過了前幾天的努力,大家應該都看得懂上方的程式碼內容,它 Render 結果會是一個待辦事項的清單:

清單的 Component 是 TodoList,假設現在我們要為該頁面增加待辦事項的筆數顯示,但又不要影響 TodoList 本身的功能(列出待辦事項),就得另外寫一個 Main Component 來當成主頁面,並且在 Main 裡使用 TodoList :

如此一來,便可以在 Main 中加入其他要顯示的資訊,但這時候因為待辦事項的資料是存在於 TodoList 的 State 裡面,要在 Main 裡取得它就得將 State 的位置提升,並透過 Props 將待辦事項的資料給 TodoList,最後 ReactDom 便只需要 Render Main 頁面:

調整後,就能夠在不影響 TodoList 的狀況下添加其他需求,例如顯示待辦事項的筆數:

上方調用 Component 和 State 的技巧叫做 State 提升,藉由將 Stata 拉到最頂層來讓所有需要該 State 的 Component 共同使用,這樣便不會導致所有 Component 都維護著自己的 State,而當 State 發生改變時,又要找出所有使用的 Component 將它改成正確的值。
那到這裡一定會有許多人疑惑,這篇不是該說明 useContext 嗎?怎麼講了一堆關於 State 的事情?
useContext 正是要解決將 State 共同管理所造成的程式碼問題。這裡的程式碼問題,並不是在執行時會有 Bug 或是出錯,而是讓程式碼不夠簡潔,怎麼說呢?
大家可以注意一下上方的 TodoList,原本待辦事項的 State 在它身上,但是因為 State 提升了,所以我們得透過 Props 將 State 送給 TodoList 接收,這麼看感覺還好,但是請試想,如果 Main 和 TodoList 間又有一層 Component 呢?而那個 Component 根本就不需要用到 State,那就會形成一個尷尬的結果:

到這裡可能有點複雜,但請大家試著消化一下,原本在 Main 直接調用的 TodoList 變成一個頁面 TodoListPage 了,而該頁面裡也許有其他內容,但只有 TodoList 需要待辦事項的 State,那待辦事項的 State 就會先傳到完全不需要使用他的 TodoListPage,才能再送到真正需要他的 TodoList。
聰明的讀者們可能會想說,那將待辦事項的 State 放到 TodoListPage 不就好了?但是在 Main 裡,還有另一個 CurrentTask 也需要用它呢......
雖然程式運行結果沒問題,但對 TodoListPage 來說,獲得 props.todoList 百分之百是多餘的程式碼,但你又不能將它刪去,因為 TodoList 需要它,那該怎麼辦?
打開傳送門吧!
以上方的例子來說,我們可以在 Main 中使用 createContext 來創建要提供的 Props 的 Component,這很容易,只需要這麼做(useContext 會在下方使用):
import React, {
useState, createContext, useContext
} from 'react';
const TodoListContext = createContext();
接下來,用剛建立的 Context 以 TodoListContext.Provider 的形式包覆 Main 所 Render 的內容,並將 Props 交給 TodoListContext.Provider 的 value,令人開心的是,到這個步驟,已經不需要再將 todoList 用 Props 提供給 TodoListPage 或 CurrentTask 了,因此可以將它移除。
以上三個部分修改完後,Main 的內容會像:

清爽多了對吧!那鏡頭轉到 TodoList 和 CurrentTask,現在少了 Props,子 Component 只能藉由 useContext 獲取 Context TodoListContext 管理的 value,當然 Props 什麼的也都可以直接拿掉:),最後 TodoList 會像:

而 CurrentTask 是:

然後可別遺漏 TodoListPage,我們得將在它之中那討厭的 Props 給刪去:

最後使用 npm run start 運行:

得到的結果會與使用 Props 相同,
而因為少了一層一層傳遞的 Props,所以當初為 Component 設置的 propTypes 也就可以都拿掉了!萬歲!
本文的範例程式碼會提供在 GitHub 上,歡迎各位參考:)
useContext 解決了透過 Props 傳遞資料時常常會經過太多層,且讓不需要該資料的 Component 也都擁有的狀況,如果只是一層還沒關係,但是如果出現文章中舉出的例子,不妨可以試著感受一下 useContext 的魅力!
如果文章中有任何問題,或是不理解的地方,都可以留言告訴我!謝謝大家!
有個問題想請教,所以使用 useContext 後,包覆有使用該 state 的子元件的元件(該子元件的父層),就不會重新渲染了嗎?
還是會哦!當 Provider 的 value 變化時,他的所有 children 還是會重新 rerender!因此我們要盡量在需要的時候使用,讓 Component 更乾淨,不會有多餘的 Code,要設置 Global 的 State 還是會推 Redux!
想請教一下
const {todolist} = props;
const todolist = useContext(TodoListContext)
這兩個程式碼的差別在哪裡呢?
第一個todolist有括號,第二個則沒有,表示前者是陣列後者是物件嗎?
有點搞不太清楚這部分的差別:((
其實第一個程式碼的意思是這樣子:
const todolost = props.todolist;
從 props 中拿出 todolist,只是方便一點的寫法而已 XD
XDDD,學到小技巧了! 感恩
const TodoListPage = () => (
<div>
<div>其他內容什麼的</div>
<TodoList />
</div>
);
會遇到錯誤
const TodoListPage = () => (
return(
<div>
<div>其他內容什麼的</div>
<TodoList />
</div>
)
);
return 後解決